/*  JEVEMon, A Java-based EVEMon clone.
Copyright (C) 2011, Raul Ruiz

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

package jeveme.datacontainers;

import eveapi.EVEApiConnector;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Calendar;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.xpath.*;
import javax.xml.parsers.*;
import jeveme.helpers.OperatingSystem;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 *
 * @author Raul Ruiz
 */
public class EVECharacter implements Serializable {
    
    /**
     * 
     */
    private String m_characterName;
    
    /**
     * 
     */
    private String m_characterID;
    
    /**
     * 
     */
    private String m_corporationName;
    
    /**
     * 
     */
    private String m_corporationID;
    
    private String m_dateOfBirth;
    private String m_race;
    private String m_gender;
    private String m_bloodLine;
    private String m_ancestry;
    
    private String m_cloneName;
    private String m_cloneSkillPoints;
    
    private String m_balance;
    
    private String m_intelligence;
    private String m_memory;
    private String m_charisma;
    private String m_perception;
    private String m_willpower;
    
    private Calendar m_cachedUntil;
    
    private ArrayList<EVESkill> m_characterSkills;
    
    private EVESkillInTraining m_skillInTraining;
    
    private EVESkillQueue m_skillQueue;
    
    private EVEAccount m_parent;
    
    /**
     * Indicated whether the character is tracked in JEVEMe.
     */
    private boolean m_tracked;
    
    public EVECharacter(EVEAccount parent_account) {
        m_characterSkills = new ArrayList<EVESkill>();
        m_skillInTraining = new EVESkillInTraining();
        m_skillQueue = new EVESkillQueue();
        m_parent = parent_account;
    }
    
    public EVEAccount getParent() {
        return m_parent;
    }
    
    public ArrayList<EVESkill> getSkillsByGroupID(String groupID) {
        ArrayList<EVESkill> skills = new ArrayList<EVESkill>();
        for(int i=0; i< m_characterSkills.size(); i++) {
            EVESkill s = m_characterSkills.get(i);
            if(s.getGroupID().equals(groupID)) {
                skills.add(s);
            }
        }
        
        return skills;
    }
    
    public ArrayList<EVESkill> getAllSkills() {
        return m_characterSkills;
    }
    
    public void addSkill(EVESkill es) {
        m_characterSkills.add(es);
    }
    
    public void clearAllSkills() {
        m_characterSkills.clear();
    }
    
    /**
     * Returns the number of skills known by this character, regardless of level.
     * @return
     */
    public int getTotalSkillsKnown() {
        return m_characterSkills.size();
    }
    
    /**
     * Returns the number of skills known by this character that are at level 5.
     * @return 
     */
    public int getTotalSkillsAtLevelFive() {
        
        int skillsAtFive = 0;
        
        for(int i=0; i<m_characterSkills.size(); i++) {
            EVESkill es = m_characterSkills.get(i);
            if(es.getTrainedLevel().equals("5")) {
                skillsAtFive++;
            }
        }
        
        return skillsAtFive;
    }
    
    /**
     * Returns the total number of skill points for the character.
     * @return 
     */
    public int getTotalSkillPoints() {
        int totalSP = 0;
        
        for(int i=0; i<m_characterSkills.size(); i++) {
            EVESkill es = m_characterSkills.get(i);
            totalSP += Integer.parseInt(es.getSkillPoints());
        }
        
        return totalSP;
    }
    
    /**
     * 
     * @return 
     */
    public String getCharacterName() {
        return m_characterName;
    }
    
    /**
     * 
     * @param newName 
     */
    public void setCharacterName(String newName) {
        m_characterName = newName;
    }
    
    /**
     * 
     * @return 
     */
    public String getCharacterID() {
        return m_characterID;
    }
    
    /**
     * 
     * @param id 
     */
    public void setCharacterID(String id) {
        m_characterID = id;
    }
    
    /**
     * 
     * @return 
     */
    public String getCorporationName() {
        return m_corporationName;
    }
    
    /**
     * 
     * @param name 
     */
    public void setCorporationName(String name) {
        m_corporationName = name;
    }
    
    /**
     * 
     * @return 
     */
    public String getCorporationID() {
        return m_corporationID;
    }
    
    /**
     * 
     * @param id 
     */
    public void setCorporationID(String id) {
        m_corporationID = id;
    }
    
    public String getDateOfBirth() {
        return m_dateOfBirth;
    }
    
    public void setDateOfBirth(String bday) {
        m_dateOfBirth = bday;
    }
    
    public String getRace() {
        return m_race;
    }
    
    public void setRace(String race) {
        m_race = race;
    }
    
    public String getBloodLine() {
        return m_bloodLine;
    }
    
    public void setBloodLine(String bloodLine) {
        m_bloodLine = bloodLine;
    }
    
    public String getAncestry() {
        return m_ancestry;
    }
    
    public void setAncestry(String ancestry) {
        m_ancestry = ancestry;
    }
    
    public String getGender() {
        return m_gender;
    }
    
    public void setGender(String gender) {
        m_gender = gender;
    }
    
    public void setCloneName(String clonename) {
        m_cloneName = clonename;
    }
    
    public String getCloneName() {
        return m_cloneName;
    }
    
    public void setCloneSkillPoints(String skillpoints) {
        m_cloneSkillPoints = skillpoints;
    }
    
    public String getCloneSkillPoints() {
        return m_cloneSkillPoints;
    }
    
    public void setBalance(String balance) {
        m_balance = balance;
    }
    
    public String getBalance() {
        return m_balance;
    }
    
    public void setIntelligence(String intelligence) {
        m_intelligence = intelligence;
    }
    
    public String getIntelligence() {
        return m_intelligence;
    }
    
    public void setMemory(String memory) {
        m_memory = memory;
    }
    
    public String getMemory() {
        return m_memory;
    }
    
    public void setCharisma(String charisma) {
        m_charisma = charisma;
    }
    
    public String getCharisma() {
        return m_charisma;
    }
    
    public void setPerception(String perception) {
        m_perception = perception;
    }
    
    public String getPerception() {
        return m_perception;
    }
    
    public void setWillpower(String willpower) {
        m_willpower = willpower;
    }
    
    public String getWillpower() {
        return m_willpower;
    }
    
    public ImageIcon getCharacterPortrait() {

        return loadPortrait();
    }
    
    /**
     * 
     * @return 
     */
    public boolean getTracked() {
        return m_tracked;
    }
    
    /**
     * 
     * @param trackAccount 
     */
    public void setTracked(boolean trackAccount) {
        m_tracked = trackAccount;
    }
    
    /**
     * Updates the character object by fetching and parsing data from the EVE Api.
     * @param threaded Determines whether the update should be threaded or a foreground task.
     */
    public void updateCharacter(boolean threaded) {
        //Check to see if this data is still cached.
        //@TODO Add Cache
        
        String userID = this.getParent().getUserID();
        String apiKey = this.getParent().getApiKey();
        EVECharacterSheetParser ecsp = new EVECharacterSheetParser(userID, apiKey, m_characterID);
        EVESkillInTrainingUpdater esitu = new EVESkillInTrainingUpdater(userID, apiKey, m_characterID);
        if(threaded) {
            new Thread(ecsp).start();
            new Thread(esitu).start();
        }
        else {
            ecsp.run();
            esitu.run();
        }
    }
    
    public ImageIcon loadPortrait() {
        
        if(m_characterID.isEmpty()) {
            return null;
        }
         
        String dirPath = System.getProperty("user.home");
        //Determine the operating system.
        if(OperatingSystem.isWindows()) {
            dirPath += "\\.JEVEMe\\Images\\";
        }
        else if(OperatingSystem.isMac()) {
            dirPath += "/Library/Application Support/JEVEMe/Images/";
        }
        else if(OperatingSystem.isUnix()) {
            dirPath += "/.jeveme/Images/";
        }
        else {
            dirPath += "/.jeveme/Images/";
        }
        
        String charactername = getCharacterName();
        charactername = charactername.replace(" ", "_");
        String fileName = charactername + "_128" + ".jpg";
        
        String filePath = dirPath + fileName;
        
        ImageIcon icon;
        
        File f = new File(filePath);
        if(f.exists()) {
            try {
                Image image = ImageIO.read(f);
                icon = new ImageIcon(image);
            }
            catch(Exception e){icon = new ImageIcon(ClassLoader.getSystemResource("resources/menuicons/default_char_pic.png"));}
            
        }
        else {
            try {
                Image image = ImageIO.read(ClassLoader.getSystemResource("jeveme/resources/menuicons/default_char_pic.png"));
                icon = new ImageIcon(image.getScaledInstance(128, 128, 0));
            }
            catch (Exception e) {icon = new ImageIcon(ClassLoader.getSystemResource("jeveme/resources/menuicons/default_char_pic.png"));};
            
        }
        
        return icon;
    }
    
    public void updateCharacterPortraitFromEVE() throws Exception {
        String url = "http://image.eveonline.com/Character/" + getCharacterID() + "_128.jpg";
        
        URL u = new URL(url);
        URLConnection uc = u.openConnection();
        String contentType = uc.getContentType();
        int contentLength = uc.getContentLength();
        if (contentType.startsWith("text/") || contentLength == -1) {
          throw new IOException("This is not a binary file.");
        }
        InputStream raw = uc.getInputStream();
        InputStream in = new BufferedInputStream(raw);
        byte[] data = new byte[contentLength];
        int bytesRead = 0;
        int offset = 0;
        while (offset < contentLength) {
          bytesRead = in.read(data, offset, data.length - offset);
          if (bytesRead == -1)
            break;
          offset += bytesRead;
        }
        in.close();

        if (offset != contentLength) {
          throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
        }
        
        String dirPath = System.getProperty("user.home");
        //Determine the operating system.
        if(OperatingSystem.isWindows()) {
            dirPath += "\\.JEVEMe\\Images\\";
        }
        else if(OperatingSystem.isMac()) {
            dirPath += "/Library/Application Support/JEVEMe/Images/";
        }
        else if(OperatingSystem.isUnix()) {
            dirPath += "/.jeveme/Images/";
        }
        else {
            dirPath += "/.jeveme/Images/";
        }
        
        String charactername = getCharacterName();
        charactername = charactername.replace(" ", "_");
        String fileName = charactername + "_128" + ".jpg";
        
        String filePath = dirPath + fileName;
        FileOutputStream out = new FileOutputStream(filePath);
        out.write(data);
        out.flush();
        out.close();

    }
    
    class EVESkillInTrainingUpdater implements Runnable {
        
        private String xml;
        private String m_userID;
        private String m_apiKey;
        private String m_characterID;
        
        public EVESkillInTrainingUpdater(String userID, String apiKey, String characterID) {
            m_userID = userID;
            m_apiKey = apiKey;
            m_characterID = characterID;
        }
        
        @Override
        public void run() {
            xml = EVEApiConnector.getCharacterSkillInTraining(m_userID, m_apiKey, m_characterID);
        }
    }
    
    class EVECharacterSheetParser implements Runnable{
        
        /**
         * The EVE Character Sheet in XML format.
         */
        private String xml;
        private String m_userID;
        private String m_apiKey;
        private String m_characterID;
        
        private final static String CHARACTERNAMEEXPRESSION = "//eveapi/result/name";
        private final static String CHARACTERDOB = "//eveapi/result/DoB";
        private final static String CHARACTERRACE = "//eveapi/result/race";
        private final static String CHARACTERBLOODLINE = "//eveapi/result/bloodLine";
        private final static String CHARACTERANCESTRY = "//eveapi/result/ancestry";
        private final static String CHARACTERGENDER = "//eveapi/result/gender";
        private final static String CHARACTERCORPNAME = "//eveapi/result/corporationName";
        private final static String CHARACTERCORPID = "//eveapi/result/corporationID";
        private final static String CHARACTERCLONENAME = "//eveapi/result/cloneName";
        private final static String CHARACTERCLONESKILLPOINTS = "//eveapi/result/cloneSkillPoints";
        private final static String CHARACTERBALANCE = "//eveapi/result/balance";
        private final static String CHARACTERINTELLIGENCE = "//eveapi/result/attributes/intelligence";
        private final static String CHARACTERMEMORY = "//eveapi/result/attributes/memory";
        private final static String CHARACTERCHARISMA = "//eveapi/result/attributes/charisma";
        private final static String CHARACTERPERCEPTION = "//eveapi/result/attributes/perception";
        private final static String CHARACTERWILLPOWER = "//eveapi/result/attributes/willpower";
        private final static String CHARACTERSKILLS = "//eveapi/result/rowset[@name=\"skills\"]/row";
        
        public EVECharacterSheetParser(String userID, String apiKey, String characterID) {
            m_userID = userID;
            m_apiKey = apiKey;
            m_characterID = characterID;
        }
        
        @Override
        public void run() {
            try {
                xml = EVEApiConnector.getCharacterSheetXML(m_userID, m_apiKey, m_characterID);
                
                DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = domFactory.newDocumentBuilder();
                org.xml.sax.InputSource is = new org.xml.sax.InputSource();
                is.setCharacterStream(new java.io.StringReader(xml));
                Document doc = builder.parse(is);
                XPath xpath = XPathFactory.newInstance().newXPath();
                
                //Character Name
                setCharacterName(getSingleNodeTextValue(doc, xpath, CHARACTERNAMEEXPRESSION));

                //Date of Birth
                setDateOfBirth(getSingleNodeTextValue(doc, xpath, CHARACTERDOB));
                
                //Race
                setRace(getSingleNodeTextValue(doc, xpath, CHARACTERRACE));
                
                //Bloodline
                setBloodLine(getSingleNodeTextValue(doc, xpath, CHARACTERBLOODLINE));
                
                //Ancestry
                setAncestry(getSingleNodeTextValue(doc, xpath, CHARACTERANCESTRY));
                
                //Gender
                setGender(getSingleNodeTextValue(doc, xpath, CHARACTERGENDER));
                
                //Corporation Name
                setCorporationName(getSingleNodeTextValue(doc, xpath, CHARACTERCORPNAME));
                
                //Corporation ID
                setCorporationID(getSingleNodeTextValue(doc, xpath, CHARACTERCORPID));
                
                //Clone Name
                setCloneName(getSingleNodeTextValue(doc, xpath, CHARACTERCLONENAME));
                
                //Clone Skill Points
                setCloneSkillPoints(getSingleNodeTextValue(doc, xpath, CHARACTERCLONESKILLPOINTS));
                
                //Balance
                setBalance(getSingleNodeTextValue(doc, xpath, CHARACTERBALANCE));
                
                //Intelligence
                setIntelligence(getSingleNodeTextValue(doc, xpath, CHARACTERINTELLIGENCE));
                //Memory
                setMemory(getSingleNodeTextValue(doc, xpath, CHARACTERMEMORY));
                //Charisma
                setCharisma(getSingleNodeTextValue(doc, xpath, CHARACTERCHARISMA));
                //Perception
                setPerception(getSingleNodeTextValue(doc, xpath, CHARACTERPERCEPTION));
                //Willpower
                setWillpower(getSingleNodeTextValue(doc, xpath, CHARACTERWILLPOWER));
                
                
                //Update the character's skills
                clearAllSkills();
                EVEMasterSkillSheet emsc = EVEMasterSkillSheet.getInstance();
                XPathExpression skills_expr = xpath.compile(CHARACTERSKILLS);
                Object skills_result = skills_expr.evaluate(doc, XPathConstants.NODESET);
                NodeList skill_nodes = (NodeList) skills_result;
                System.out.println("About to process char skills");
                for(int i=0; i< skill_nodes.getLength(); i++) {
                    System.out.println("Entered for loop");
                    Node skill_node = skill_nodes.item(i);
                    NamedNodeMap skill_attributes = skill_nodes.item(i).getAttributes();
                    if(!skill_node.getNodeName().equals("row")) {
                        continue;
                    }
                    String skill_typeID = skill_attributes.getNamedItem("typeID").getNodeValue();
                    String skill_skillPoints = skill_attributes.getNamedItem("skillpoints").getNodeValue();
                    String skill_level = skill_attributes.getNamedItem("level").getNodeValue();
                    String skill_published = skill_attributes.getNamedItem("published").getNodeValue();
                    
                    EVESkill es = emsc.getEVESkillByID(skill_typeID);
                    es.setPublished(skill_published);
                    es.setTrainedLevel(skill_level);
                    es.setSkillPoints(skill_skillPoints);
                    addSkill(es);
                    
                }

            }
            catch(Exception e) {System.out.println(e.getMessage());}
            
        }
        
        /**
         * Gets the value of a single node in the character sheet XML.
         * @param doc
         * @param xpath
         * @param expression
         * @return 
         */
        private String getSingleNodeTextValue(Document doc, XPath xpath, String expression) {
            try {
                XPathExpression expr = xpath.compile(expression);
                Object expr_result = expr.evaluate(doc, XPathConstants.NODESET);
                NodeList nodes = (NodeList) expr_result;
                Element e = (Element) nodes.item(0);
                return e.getTextContent();
            }
            catch(Exception e) {return null;}
        }
    }
    
}
